//
//  SADModelOnBus.cpp
//  sad
//
//  Created by Christopher Kemsley on 2/5/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//


#include "MemoryInterface.hpp"
#include "SystemCDefinitions.hpp"

#include "Settings.hpp"
#include "Codec.hpp"
#include "ModuleFactory.hpp"
#include "PubSub.hpp"

#include <string>
#include <stdio.h>

#include "Log.hpp"

#include "BusInterface.hpp"

using namespace Bus ;



#define NUM_BLOCKS			64
#define BLOCK_SIZE			64
#define INPUT1_ADDR			0
#define INPUT2_ADDR			16384
#define SAD_OUTPUT_ADDR		32768





struct	SADModelOnBusSettings : public Settings
{
	std::string		masterName ;
	std::string		resetName ;
	
	unsigned		blockCount ;
	
	unsigned		blockSize ;
	
	unsigned		input1Address ;
	unsigned		input2Address ;
	unsigned		outputAddress ;
	
	bool			useDelays ;
	SCInterval		compareDelay ;
	SCInterval		addDelay ;
	SCInterval		subtractDelay ;
	
	
	
	SADModelOnBusSettings ( std::string const & name )
	:	Settings(name)
	,	masterName		( "bus.master" )
	,	resetName		( "globalReset" )
	,	blockCount		( NUM_BLOCKS )
	,	blockSize		( BLOCK_SIZE )
	,	input1Address	( INPUT1_ADDR )
	,	input2Address	( INPUT2_ADDR )
	,	outputAddress	( SAD_OUTPUT_ADDR )
	,	useDelays		( false )
	,	compareDelay	( 0 , kSCIntervalSeconds )
	,	addDelay		( 0 , kSCIntervalSeconds )
	,	subtractDelay	( 0 , kSCIntervalSeconds )
		{
		add ( "masterName" , masterName ) ;
		add ( "resetSignalName" , resetName ) ;
		
		add ( "blockCount" , blockCount ) ;
		add ( "blockSize" , blockSize ) ;
		
		add ( "input1Address" , input1Address ) ;
		add ( "input2Address" , input2Address ) ;
		add ( "outputAddress" , outputAddress ) ;
		
		add ( "useDelays" , useDelays ) ;
		add ( "delay" , compareDelay ) ;
		add ( "delay" , addDelay ) ;
		add ( "delay" , subtractDelay ) ;
		}
} ;









struct	SADModelOnBus : public ConfiguredModel<SADModelOnBusSettings>
{
	sc_port<MasterInterface>	mBusMaster ;
	SCInputSignal<SCLogic>		mReset ;
	
	
	SC_HAS_PROCESS ( SADModelOnBus ) ;
	
	/** Construction **/
	
	SADModelOnBus ( ModelVariant const & name )
	:	ConfiguredModel<SADModelOnBusSettings> ( name )
		{
		Subscription<MasterInterface>	pBusMaster ( mConfig.masterName ) ;
		bindPortToInterface ( &mBusMaster , pBusMaster ) ;
		
		mReset = mConfig.resetName ;
		
		SC_THREAD ( runLoop ) ;
		}
	
	
	/*
	 We will replace operations which would use the ALU with the following
	 methods. These methods will, if delays are enabled, run the appropriate
	 wait statement.
	 */
	
	void	_op_increment		( unsigned & value )
		{
		_op_add ( value , 1 , value ) ; 
		}
	bool	_op_isLessThan		( unsigned thisNumber , unsigned isLessThanThis )
		{
		if(mConfig.useDelays)wait(mConfig.compareDelay) ;
		return thisNumber < isLessThanThis ;
		}
	bool	_op_isEqualTo		( unsigned thisNumber , unsigned isEqualToThis )
		{
		if(mConfig.useDelays)wait(mConfig.compareDelay) ;
		return thisNumber == isEqualToThis ;
		}
	bool	_op_isNotEqualTo	( unsigned thisNumber , unsigned isNotEqualToThis )
		{
		if(mConfig.useDelays)wait(mConfig.compareDelay) ;
		return thisNumber != isNotEqualToThis ;
		}
	void	_op_subtract		( unsigned takeThis , unsigned subtractThis , unsigned & out )
		{
		if(mConfig.useDelays)wait(mConfig.subtractDelay) ;
		out = takeThis - subtractThis ;
		}
	void	_op_add				( unsigned in1 , unsigned in2 , unsigned & out )
		{
		if(mConfig.useDelays)wait(mConfig.addDelay) ;
		out = in1 + in2 ;
		}
	
	
	
	
	
	/*
	 This performs the actual computation. It uses only basic pointer manipulation
	 through addition & subtraction and basic comparisons. The algorithm, with respect
	 to ALU operations, is optimized as a compiler would do.
	 */
	
	void	runLoop ()
		{
		if ( *mReset != 0 )
			{
			wait ( mReset->posedge_event() ) ;
			wait ( mReset->negedge_event() ) ;
			}
		
		SCInterval	begin	=	SCCurrentTime() ;
		Log::output() << "Beginning SAD at " << begin << "\n" ;
		
		
		
		unsigned	i;
		unsigned	block;
		unsigned	sad ;
		
		unsigned	offset	=	0 ;
		
		unsigned	pInput1	=	mConfig.input1Address ;
		unsigned	pInput2	=	mConfig.input2Address ;
		unsigned	pOutput	=	mConfig.outputAddress ;
		
		
		for ( block=0 ; _op_isLessThan(block,mConfig.blockCount) ; _op_increment(block) )
			{
			unsigned		pO		=	(offset+mConfig.outputAddress) ;
			
			sad = 0;
			for ( i=0 ; _op_isLessThan(i,mConfig.blockSize) ; _op_increment(i) ) 
				{
				Log::states("SADModelOnBus") << "...sad = (*"<<pInput1<<") + (*"<<pInput2<<")" ;
				unsigned	v1 ;
				unsigned	v2 ;
				
				bool		o1	=	false ; // mBusMaster->Read(pInput1,v1) ;
				bool		o2	=	false ; // mBusMaster->Read(pInput2,v2) ;
				
				// Get the data from the bus
					{
					// Read value 1
						{
						Request	request	( pInput1 , kOperationRead ) ;
						mBusMaster -> request ( Bus::uniqueAddress ( static_cast<void*>(this) ) , request ) ;
						if ( mBusMaster -> waitForAcknowledge(Bus::uniqueAddress ( static_cast<void*>(this) )) )
							{
							o1 = true ;
							Word w ;
							mBusMaster -> readWord ( w ) ;
							v1 = w ;
							}
						else
							{
							Log::notice("SADModelOnBus") << "Failed to read SAD input 1 from " << pInput1 ;
							}
						}
					
					// Read value 2
						{
						Request	request	( pInput2 , kOperationRead ) ;
						mBusMaster -> request ( Bus::uniqueAddress ( static_cast<void*>(this) ) , request ) ;
						if ( mBusMaster -> waitForAcknowledge(Bus::uniqueAddress ( static_cast<void*>(this) )) )
							{
							o2 = true ;
							Word w ;
							mBusMaster -> readWord ( w ) ;
							v2 = w ;
							}
						else
							{
							Log::notice("SADModelOnBus") << "Failed to read SAD input 2 from " << pInput2 ;
							}
						}
					}
				
				Log::states("SADModelOnBus") << "...sad = ("<<v1<<") + ("<<v2<<")" ;
				Log::states("SADModelOnBus") << "...sad: " << (o1?"OK":"Fail!") << " + " << (o1?"OK":"Fail!") ;
				
				if ( _op_isNotEqualTo(o1,0) )		// if it's true
					{
					if ( _op_isNotEqualTo(o2,0) )	// if it's true
						{
						unsigned v ;
						if ( _op_isLessThan(v1,v2) )
							_op_subtract(v2,v1,v) ;
						else
							_op_subtract(v1,v2,v) ;
						_op_add ( sad , v , sad ) ;
						}
					}
				
				// Update states (pointers, etc)
				_op_increment ( pInput1 ) ;
				_op_increment ( pInput2 ) ;
				}
			
			Log::states("SADModelOnBus") << "...(*"<<pOutput<<") = " << sad ;
			
			
			// Perform the write
			//mBusMaster -> Write ( pOutput , sad ) ;
			// Write the data to the bus
				{
				Request	request	( pOutput , kOperationWrite ) ;
				mBusMaster -> request ( Bus::uniqueAddress ( static_cast<void*>(this) ) , request ) ;
				if ( mBusMaster -> waitForAcknowledge(Bus::uniqueAddress ( static_cast<void*>(this) )) )
					{
					Word w ( sad ) ;
					mBusMaster -> writeWord ( w ) ;
					}
				else
					{
					Log::notice("SADModelOnBus") << "Failed to write SAD value to " << pOutput ;
					}
				}
			Log::output() << "**** Simulation time: " << (SCCurrentTime()) << " ****" ;
			
			
			// Disabled verification
			Log::output() << "SAD is Writing " << sad << " to " << pO ;
			/*mBusMaster -> Read ( pOutput , sadVerification ) ;
			if ( sadVerification != sad )
				Log::error("SADModelOnBus") << "Verification of address " << pO << " failed!\n" ;*/
			
			
			// Update states (pointers, etc)
			_op_add ( offset , mConfig.blockSize , offset ) ;
			_op_increment ( pOutput ) ;
			}
		
		
		SCInterval	end		=	SCCurrentTime() ;
		Log::output() << "Ending SAD at " << end ;
		Log::output() << "Duration: " << end-begin ;
		}
	
} ;

TypedModuleMaker<SADModelOnBus>	_SADModelOnBus ( "bus.SAD" ) ;

